#pragma once

#include <cassert>
#include <string>
#include "Vectors.hpp"

namespace cge {
	template< class T >
	class BasicBigMatrix {
	public:
		typedef std::function<void (int x, int y, T&)> ApplyFuncInt;
		typedef std::function<void (const vec2&, T&)>  ApplyFuncVec2;
		typedef std::function<void (T&)>               ApplyFuncSimple;

		BasicBigMatrix(unsigned m, unsigned n): m_(m),n_(n) {
			v_ = new T[m*n];
		}

		BasicBigMatrix(unsigned m, unsigned n, const T& init): m_(m), n_(n) {
			unsigned l = m*n;
			v_ = new T[l];
			for(unsigned i=0; i<l; ++i)
				v_[i] = init;
		}

		BasicBigMatrix(const BasicBigMatrix &rhs) {
			m_ = rhs.m_;
			n_ = rhs.n_;
			unsigned l = m_*n_;
			v_ = new T[l];
			for(unsigned i=0; i<l; ++i)
				v_[l] = rhs.v_[l];
		}
		
		~BasicBigMatrix() {
			delete[] v_;
		}

		unsigned getHeight() const { return m_; }
		unsigned getWidth() const { return n_; }

		BasicBigMatrix& operator=( const BasicBigMatrix &rhs ) {
			assert( m_ == rhs.m_ );
			assert( n_ == rhs.n_ );
			unsigned l = m_*n_;
			for(unsigned i=0; i<l; ++i)
				v_[i] = rhs.v_[i];
			return *this;
		}

		T& operator()(unsigned i) {
			assert( i < m_*n_ );
			return v_[i];
		}

		T& operator()(unsigned i, unsigned j) {
			assert( i < m_ && j < n_ );
			return v_[i*n_+j];
		}

		const T& operator()(unsigned i) const {
			assert( i < m_*n_ );
			return v_[i];
		}

		const T& operator()(unsigned i, unsigned j) const {
			assert( i < m_ && j < n_ );
			return v_[i*n_+j];
		}

		BasicBigMatrix& operator*=( T scalar ) {
			unsigned l = m_*n_;
			for(unsigned i=0; i<l; ++i)
				v_[i] *= scalar;
			return *this;
		}

		BasicBigMatrix& operator/=( T scalar ) {
			return (*this)*=(1.0/scalar);
		}

		void apply( const ApplyFuncInt& function ) {
			for(unsigned i=0; i<m_; ++i)
				for(unsigned j=0; j<n_; ++j)
					function( i,j, (*this)(i,j) );
		}

		void apply( const ApplyFuncVec2&   function ) {
			for(unsigned i=0; i<m_; ++i)
				for(unsigned j=0; j<n_; ++j)
					function( vec2(i,j), (*this)(i,j) );
		}

		void apply( const ApplyFuncSimple& function ) {
			unsigned l = m_*n_;
			for(unsigned i=0; i<l; ++i)
				function(v_[i]);
		}

	private:
		unsigned m_, n_;
		T *v_;
	};

	typedef BasicBigMatrix<double> BigMatrixd;
	typedef BasicBigMatrix<vec4>   BigMatrixVec4;
}